// ScopeChain.java
// FESI Copyright (c) Jean-Marc Lugrin, 1999
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

package FESI.Interpreter;

import FESI.Data.ESObject;
import FESI.Data.ESReference;
import FESI.Data.ESValue;
import FESI.Exceptions.EcmaScriptException;

/**
 * The scope chaine has established by WITH statements. Used to lookup values by
 * name.
 */
public class ScopeChain implements java.io.Serializable {
    private static final long serialVersionUID = -6458140452839959671L;
    private final ScopeChain previousElement;
    private final ESObject thisElement;
    private final boolean provideThis;

    /**
     * CReate a new scope chain linked to a previous one (which is null only for
     * the topmost chain)
     * 
     * @param thisElement
     *            Object to look at at this level
     * @param previousElement
     *            previous object in scope chain
     * @deprecated 
     */
    @Deprecated
    public ScopeChain(ESObject thisElement, ScopeChain previousElement) {
        this(thisElement,previousElement,false);
    }

    /**
     * CReate a new scope chain linked to a previous one (which is null only for
     * the topmost chain)
     * 
     * @param thisElement
     *            Object to look at at this level
     * @param previousElement
     *            previous object in scope chain
     */
    public ScopeChain(ESObject thisElement, ScopeChain previousElement, boolean provideThis) {
        this.previousElement = previousElement;
        this.thisElement = thisElement;
        this.provideThis = provideThis;
    }

    /**
     * Return the previous element in scope chain
     * 
     * @return The previous element
     */
    ScopeChain previousScope() {
        return previousElement;
    }

    /**
     * Return a reference to an object in the scope chain, so that the value can
     * be accessed or modified.
     * 
     * @param identifier
     *            The name of the object
     * @return an ESReference object
     * @exception EcmaScriptException
     *                Not thrown
     */
    public ESReference getReference(String identifier)
            throws EcmaScriptException {
        return getReference(identifier, identifier.hashCode());
    }

    /**
     * Return a reference to an object in the scope chain, so that the value can
     * be accessed or modified.
     * 
     * @param identifier
     *            The name of the object
     * @param hash
     *            The hashCode of this identifier
     * @return an ESReference object
     * @exception EcmaScriptException
     *                Not thrown
     */
    public ESReference getReference(String identifier, int hash)
            throws EcmaScriptException {
        ScopeChain theChain = this;
        do {
            if (theChain.thisElement.hasProperty(identifier, hash)) {
                return new ESReference(theChain.thisElement, identifier, hash);
            }
            theChain = theChain.previousElement;
        } while (theChain != null);
        return new ESReference(null, identifier, hash);
    }

    /**
     * Return a value for an object in the scope chain. return ESUndefined for
     * undefined properties of existing objects, but generate an error for
     * unexistent object.
     * <P>
     * A variant of getProperty is used, which will call recursively this
     * routine and the getProperty in a previous scope until all scopes have
     * been examined or a value is returned. This avoid the call of hasProperty
     * followed by a second call to getProperty to get the property value.
     * 
     * @param identifier
     *            The name of the object
     * @return The value of the object, possibly ESUndefined
     * @exception EcmaScriptException
     *                if no global object has that value
     */
    public ESValue getValue(String identifier) throws EcmaScriptException {
        return getValue(identifier, identifier.hashCode());
    }

    /**
     * Return a value for an object in the scope chain. return ESUndefined for
     * undefined properties of existing objects, but generate an error for
     * unexistent object.
     * <P>
     * A variant of getProperty is used, which will call recursively this
     * routine and the getProperty in a previous scope until all scopes have
     * been examined or a value is returned. This avoid the call of hasProperty
     * followed by a second call to getProperty to get the property value.
     * 
     * @param identifier
     *            The name of the object
     * @param hash
     *            The hash code of the element (for optimization)
     * @return The value of the object, possibly ESUndefined
     * @exception EcmaScriptException
     *                if no global object has that value
     */
    public ESValue getValue(String identifier, int hash)
            throws EcmaScriptException {
        return thisElement
                .getPropertyInScope(identifier, previousElement, hash);
    }

    /**
     * Call a function defined by name in the scope chain.
     * <P>
     * A variant of doCall is used, which will call recursively this routine and
     * the doCall in a previous scope until all scopes have been examined or a
     * value is returned. This avoid the call of hasProperty followed by a
     * second call to getProperty to get the property value.
     * 
     * @param evaluator
     *            The evaluator doing the call
     * @param thisObject
     *            The 'this' object of the call
     * @param functionName
     *            The name of the function to call
     * @param hash
     *            The hash code of the function name (for optimization)
     * @param arguments
     *            The parameters of the call
     * @return The result of the call, possibly NULL
     * @exception EcmaScriptException
     *                for any error
     */
    public ESValue doIndirectCall(Evaluator evaluator, ESObject thisObject,
            String functionName, int hash, ESValue[] arguments)
            throws EcmaScriptException {
        return thisElement.doIndirectCallInScope(evaluator, previousElement,
                provideThis?thisElement:thisObject, functionName, hash, arguments);
    }

}